#include <Interpreters/ProcessorsProfileLog.h>

#include <Core/Settings.h>
#include <DataTypes/DataTypeArray.h>
#include <DataTypes/DataTypeDate.h>
#include <DataTypes/DataTypeDateTime.h>
#include <DataTypes/DataTypeDateTime64.h>
#include <DataTypes/DataTypeLowCardinality.h>
#include <DataTypes/DataTypeNullable.h>
#include <DataTypes/DataTypeString.h>
#include <DataTypes/DataTypesNumber.h>
#include <IO/WriteBufferFromString.h>
#include <Interpreters/Context.h>
#include <Processors/Port.h>
#include <QueryPipeline/printPipeline.h>
#include <base/getFQDNOrHostName.h>
#include <Common/ClickHouseRevision.h>
#include <Common/DateLUTImpl.h>
#include <Common/logger_useful.h>

namespace DB
{

namespace Setting
{
extern const SettingsBool log_processors_profiles;
}

ColumnsDescription ProcessorProfileLogElement::getColumnsDescription()
{
    return ColumnsDescription
    {
        {"hostname", std::make_shared<DataTypeLowCardinality>(std::make_shared<DataTypeString>()), "Hostname of the server executing the query."},
        {"event_date", std::make_shared<DataTypeDate>(), "The date when the event happened."},
        {"event_time", std::make_shared<DataTypeDateTime>(), "The date and time when the event happened."},
        {"event_time_microseconds", std::make_shared<DataTypeDateTime64>(6), "The date and time with microseconds precision when the event happened."},

        {"id", std::make_shared<DataTypeUInt64>(), "ID of processor."},
        {"parent_ids", std::make_shared<DataTypeArray>(std::make_shared<DataTypeUInt64>()), "Parent processors IDs."},
        {"plan_step", std::make_shared<DataTypeUInt64>(), "ID of the query plan step which created this processor. The value is zero if the processor was not added from any step."},
        {"plan_step_name", std::make_shared<DataTypeString>(), "Name of the query plan step which created this processor. The value is empty if the processor was not added from any step."},
        {"plan_step_description", std::make_shared<DataTypeString>(), "Description of the query plan step which created this processor. The value is empty if the processor was not added from any step."},
        {"plan_group", std::make_shared<DataTypeUInt64>(), "Group of the processor if it was created by query plan step. A group is a logical partitioning of processors added from the same query plan step. Group is used only for beautifying the result of EXPLAIN PIPELINE result."},

        {"initial_query_id", std::make_shared<DataTypeString>(), "ID of the initial query (for distributed query execution)."},
        {"query_id", std::make_shared<DataTypeString>(), "ID of the query."},
        {"name", std::make_shared<DataTypeLowCardinality>(std::make_shared<DataTypeString>()), "Name of the processor."},
        {"elapsed_us", std::make_shared<DataTypeUInt64>(), "Number of microseconds this processor was executed."},
        {"input_wait_elapsed_us", std::make_shared<DataTypeUInt64>(), "Number of microseconds this processor was waiting for data (from other processor)."},
        {"output_wait_elapsed_us", std::make_shared<DataTypeUInt64>(), "Number of microseconds this processor was waiting because output port was full."},
        {"input_rows", std::make_shared<DataTypeUInt64>(), "The number of rows consumed by processor."},
        {"input_bytes", std::make_shared<DataTypeUInt64>(), "The number of bytes consumed by processor."},
        {"output_rows", std::make_shared<DataTypeUInt64>(), "The number of rows generated by processor."},
        {"output_bytes", std::make_shared<DataTypeUInt64>(), "The number of bytes generated by processor."},
        {"processor_uniq_id", std::make_shared<DataTypeString>(), "The uniq processor id in pipeline."},
        {"step_uniq_id", std::make_shared<DataTypeString>(), "The uniq step id in plan."},
    };
}

void ProcessorProfileLogElement::appendToBlock(MutableColumns & columns) const
{
    size_t i = 0;

    columns[i++]->insert(getFQDNOrHostName());
    columns[i++]->insert(DateLUT::instance().toDayNum(event_time).toUnderType());
    columns[i++]->insert(event_time);
    columns[i++]->insert(event_time_microseconds);

    columns[i++]->insert(id);
    {
        Array parent_ids_array;
        parent_ids_array.reserve(parent_ids.size());
        for (const UInt64 parent : parent_ids)
            parent_ids_array.emplace_back(parent);
        columns[i++]->insert(parent_ids_array);
    }

    columns[i++]->insert(plan_step);
    columns[i++]->insert(plan_step_name);
    columns[i++]->insert(plan_step_description);
    columns[i++]->insert(plan_group);
    columns[i++]->insertData(initial_query_id.data(), initial_query_id.size());
    columns[i++]->insertData(query_id.data(), query_id.size());
    columns[i++]->insertData(processor_name.data(), processor_name.size());
    columns[i++]->insert(elapsed_us);
    columns[i++]->insert(input_wait_elapsed_us);
    columns[i++]->insert(output_wait_elapsed_us);
    columns[i++]->insert(input_rows);
    columns[i++]->insert(input_bytes);
    columns[i++]->insert(output_rows);
    columns[i++]->insert(output_bytes);
    columns[i++]->insert(processor_uniq_id);
    columns[i++]->insert(step_uniq_id);
}

std::vector<IProcessor::ProcessorsProfileLogInfo> getProcessorsProfileLogInfo(const Processors & processors)
{
    std::vector<IProcessor::ProcessorsProfileLogInfo> infos;
    infos.reserve(processors.size());

    for (const auto & processor : processors)
    {
        infos.push_back(processor->getProcessorsProfileLogInfo());
    }

    return infos;
}

void logProcessorProfile(ContextPtr context, const std::vector<IProcessor::ProcessorsProfileLogInfo> & profile_infos, String pipeline_dump)
{
    const Settings & settings = context->getSettingsRef();
    if (settings[Setting::log_processors_profiles])
    {
        if (auto processors_profile_log = context->getProcessorsProfileLog())
        {
            ProcessorProfileLogElement processor_elem;

            const auto time_now = std::chrono::system_clock::now();
            processor_elem.event_time = timeInSeconds(time_now);
            processor_elem.event_time_microseconds = timeInMicroseconds(time_now);
            processor_elem.initial_query_id = context->getInitialQueryId();
            processor_elem.query_id = context->getCurrentQueryId();

            for (const auto & info : profile_infos)
            {
                processor_elem.id = info.id;
                processor_elem.parent_ids = info.parent_ids;

                processor_elem.plan_step = info.plan_step;
                processor_elem.plan_step_name = info.plan_step_name;
                processor_elem.plan_step_description = info.plan_step_description;
                processor_elem.plan_group = info.plan_group;
                processor_elem.processor_uniq_id = info.processor_uniq_id;
                processor_elem.step_uniq_id = info.step_uniq_id;

                processor_elem.processor_name = info.processor_name;

                processor_elem.elapsed_us = info.elapsed_us;
                processor_elem.input_wait_elapsed_us = info.input_wait_elapsed_us;
                processor_elem.output_wait_elapsed_us = info.output_wait_elapsed_us;

                processor_elem.input_rows = info.input_rows;
                processor_elem.input_bytes = info.input_bytes;
                processor_elem.output_rows = info.output_rows;
                processor_elem.output_bytes = info.output_bytes;

                processors_profile_log->add(processor_elem);
            }
        }
        auto logger = ::getLogger("ProcessorProfileLog");
        LOG_TEST(logger, "Processors profile log:\n{}", pipeline_dump);
    }
}

void logProcessorProfile(ContextPtr context, const Processors & processors)
{
    String pipeline_dump;
    {
        WriteBufferFromString out(pipeline_dump);
        printPipeline(processors, out, true);
    }

    auto profile_infos = getProcessorsProfileLogInfo(processors);
    logProcessorProfile(context, profile_infos, pipeline_dump);
}
}
